package com.liming.gateway.fliter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.liming.common.constant.RedisConstants;
import com.liming.common.util.AntPathMatchUtils;
import com.liming.common.util.JwtUtils;
import com.liming.common.util.ResToJsonWriteUtils;
import com.liming.gateway.config.PermitPath;
import com.liming.service.redis.RedisClient;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

import static com.liming.common.constant.AuthErrorConstants.*;
import static com.liming.common.constant.SystemConstants.*;


/**
 * @author liming
 * @date 2025/1/22
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final PermitPath permitPath;
    private final RedisClient redisClient;

    /**
     * @param exchange ServerWebExchange Spring WebFlux 中的一个核心接口，表示一次完整的 HTTP 请求和响应交换
     * @param chain    GatewayFilterChain 网关过滤器链，用于将当前请求传递给下一个过滤器进行处理
     * @return Mono<Void>，表示异步操作的响应体，用于在过滤器链中传递处理结果
     */
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("网关全局过滤器 --- 请求路径:{}", exchange.getRequest().getPath());
        // 无需权限
        if (AntPathMatchUtils.permitPath(permitPath.getList(), exchange.getRequest().getPath().toString())) {
            return chain.filter(exchange);
        }
        // 鉴权
        return checkAuthorization(exchange, chain);
    }

    /**
     * 鉴权函数
     *
     * @param exchange ServerWebExchange Spring WebFlux 中的一个核心接口，表示一次完整的 HTTP 请求和响应交换
     * @param chain    GatewayFilterChain 网关过滤器链，用于将当前请求传递给下一个过滤器进行处理
     * @return Mono<Void> 表示异步操作的响应体，用于在过滤器链中传递处理结果
     */
    @SneakyThrows
    private Mono<Void> checkAuthorization(ServerWebExchange exchange, GatewayFilterChain chain) {
        List<String> authorizationList = exchange.getRequest().getHeaders().get(TOKEN_KEY);
        if (ObjectUtil.isNull(authorizationList) || CollectionUtil.isEmpty(authorizationList)) {
            return ResToJsonWriteUtils.write(exchange, TOKEN_IS_NULL);
        }
        try {
            // 获取第一个 Authorization 头字段的值
            String token = authorizationList.get(0);
            if (JwtUtils.verify(token)) {
                log.info("TOKEN --- ONE --- END");
                return againVerify(exchange, chain, token);
            }
        } catch (Exception e) {
            log.error(TOKEN_CHECK_FAILED, e);
            return ResToJsonWriteUtils.write(exchange, TOKEN_CHECK_FAILED);
        }
        // 校验失败
        log.error("{}接口不放行", exchange.getRequest().getPath());
        return ResToJsonWriteUtils.write(exchange);
    }

    /**
     * 二次校验Token
     *
     * @param exchange ServerWebExchange Spring WebFlux 中的一个核心接口，表示一次完整的 HTTP 请求和响应交换
     * @param chain    GatewayFilterChain 网关过滤器链，用于将当前请求传递给下一个过滤器进行处理
     * @param token    Token
     * @return Mono<Void> 表示异步操作的响应体，用于在过滤器链中传递处理结果
     * @throws JsonProcessingException JsonProcessingException
     */
    private Mono<Void> againVerify(ServerWebExchange exchange, GatewayFilterChain chain, String token) throws JsonProcessingException {
        String koi = JwtUtils.getKoiHeader(token);
        String id = JwtUtils.getIdFromToken(token);
        String key = null;
        if (ObjectUtil.isNull(id)) {
            return ResToJsonWriteUtils.write(exchange, PAYLOAD_DONT_HAVE_ID);
        }
        if (ObjectUtil.isNull(koi)) {
            return ResToJsonWriteUtils.write(exchange, HEADER_DONT_HAVE_KOI);
        }
        if (ObjectUtil.equal(koi, ADMIN)) {
            key = RedisConstants.ADMIN_KEY + id;
        } else if (ObjectUtil.equal(koi, USER)) {
            key = RedisConstants.USER_KEY + id;
        }
        if (ObjectUtil.isNull(key)) {
            return ResToJsonWriteUtils.write(exchange, DONT_JUDGE_KOI);
        }
        String redisToken = null;
        try {
            log.info("Redis 查询 key:{}", key);
            redisToken = (String) redisClient.valueGet(key);
        } catch (Exception e) {
            log.error("Redis 查询失败，采用默认信任策略", e);
            // 默认信任当前 Token
            ServerWebExchange newExchange = exchange.mutate().request(builder -> builder.header(ID, id)).build();
            log.info("TOKEN校验2通过（默认信任策略）");
            return chain.filter(newExchange);
        }
        if (ObjectUtil.isNull(redisToken)) {
            return ResToJsonWriteUtils.write(exchange, REDIS_DONT_HAVE_TOKEN);
        }
        if (ObjectUtil.notEqual(redisToken, token)) {
            return ResToJsonWriteUtils.write(exchange, LOGIN_FROM_A_DIFFERENT_LOCATION);
        }
        ServerWebExchange newExchange = exchange.mutate().request(builder -> builder.header(ID, id)).build();
        log.info("TOKEN校验 --- END");
        return chain.filter(newExchange);
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序 越小越先执行
        return 0;
    }
}
